<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
<head>
  <link href="//gmpg.org/xfn/11" rel="profile">
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="generator" content="Hugo 0.68.3" />

  
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>进程&amp;线程&amp;协程 &middot; 云淡风轻</title>

  
  <link type="text/css" rel="stylesheet" href="/my_technology_blog/css/print.css" media="print">
  <link type="text/css" rel="stylesheet" href="/my_technology_blog/css/poole.css">
  <link type="text/css" rel="stylesheet" href="/my_technology_blog/css/syntax.css">
  <link type="text/css" rel="stylesheet" href="/my_technology_blog/css/hyde.css">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Abril+Fatface|PT+Sans:400,400i,700">


  
  <link rel="apple-touch-icon-precomposed" sizes="144x144" href="/apple-touch-icon-144-precomposed.png">
  <link rel="shortcut icon" href="/favicon.png">

  
  
</head>

  <body class=" ">
  <aside class="sidebar">
  <div class="container sidebar-sticky">
    <div class="sidebar-about">
      <a href="/my_technology_blog/"><h1>云淡风轻</h1></a>
      <p class="lead">
       这里是人凤的个人博客 
      </p>
    </div>

    <nav>
      <ul class="sidebar-nav">
        <li><a href="/my_technology_blog/">Home</a> </li>
        <li><a href="http://liu_ren_feng.gitee.io/my_technology_blog/tree/"> 个人简历 </a></li>
      </ul>
    </nav>

  </div>
</aside>

    <main class="content container">
    <div class="post">
  <h1>进程&amp;线程&amp;协程</h1>
  <time datetime=2018-02-07T19:21:56&#43;0800 class="post-date">Wed, Feb 7, 2018</time>
  <h1 id="进程-">进程</h1>
<h2 id="进程定义-">进程定义</h2>
<ol>
<li>进程是资源分配最小单位</li>
<li>当一个可执行程序被系统执行（分配内存等资源）就变成了一个进程</li>
<li>程序并不能单独运行，只有将程序装载到内存中，系统为它分配资源才能运行，这种执行的程序就称之为进程</li>
<li>程序和进程的区别就在于：程序是指令的集合，它是进程运行的静态描述文本；进程是程序的一次执行活动，属于动态概念</li>
<li>在多道编程中，我们允许多个程序同时加载到内存中，在操作系统的调度下，可以实现并发地执行。</li>
<li>进程的出现让每个用户感觉到自己独享CPU，因此，进程就是为了在CPU上实现多道编程而提出的。</li>
<li>进程之间有自己独立的内存，各进程之间不能相互访问</li>
<li>创建一个新线程很简单，创建新进程需要对父进程进行复制</li>
</ol>
<h2 id="多道编程概念-">多道编程概念</h2>
<p>多道编程： 在计算机内存中同时存放几道相互独立的程序，他们共享系统资源，相互穿插运行</p>
<p>单道编程： 计算机内存中只允许一个的程序运行</p>
<h2 id="进程优点-">进程优点：</h2>
<p>提供了多道编程，让我们感觉我们每个人都拥有自己的CPU和其他资源，可以提高计算机的利用率</p>
<h2 id="进程的两个重要缺点-">进程的两个重要缺点</h2>
<p>a. 第一点：进程只能在一个时间干一件事，如果想同时干两件事或多件事，进程就无能为力了。</p>
<p>b. 第二点：进程在执行的过程中如果阻塞，即使进程中有些工作不依赖于输入的数据，也将无法执行（例如等待输入，整个进程就会挂起）。</p>
<p>c. 例如，我们在使用qq聊天， qq做为一个独立进程如果同一时间只能干一件事，那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息,
即操作系统处理一会你的qq任务，又切换到word文档任务上了，每个cpu时间片分给你的qq程序时，你的qq还是只能同时干一件事呀</p>
<h2 id="进程间互相访问数据的四种方法-">进程间互相访问数据的四种方法</h2>
<p>注：不同进程间内存是不共享的，所以互相之间不能访问对方数据</p>
<p>法1:  利用Queues实现父进程到子进程（或子进程间）的数据传递</p>
<p>法2:  使用管道pipe实现两个进程间数据传递</p>
<p>法3:  Managers实现很多进程间数据共享</p>
<p>法4：借助redis中间件进行数据共享</p>
<ol start="4">
<li>
<p>进程池</p>
<pre><code> from  multiprocessing import Process,Pool
	
 import time,os
	
 def foo(i):
	
     time.sleep(2)
	
     print(&quot;in the process&quot;,os.getpid()) #打印子进程的pid
	
     return i+100
	
 def call(arg):
	
     print('--&gt;exec done:',arg,os.getpid())
	
 if__name__ == '__main__':
	
     pool = Pool(3)          #进程池最多允许5个进程放入进程池
	
 print(&quot;主进程pid：&quot;,os.getpid())     #打印父进程的pid
	
     for i in range(10)
</code></pre>
</li>
</ol>
<p>用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数</p>
<pre><code>    pool.apply_async(func=foo, args=(i,),callback=call)
</code></pre>
<p>用法2 串行 启动进程不在用Process而是直接用pool.apply()</p>
<pre><code>    pool.apply(func=foo, args=(i,))
    print('end')
        pool.close()    #关闭pool
        pool.join()     #进程池中进程执行完毕后再关闭，如果注释，那么程序直接关闭。
</code></pre>
<h2 id="进程和程序的区别-">进程和程序的区别</h2>
<ol>
<li>程序只是一个普通文件，是一个机器代码指令和数据的集合，所以，程序是一个静态的实体</li>
<li>而进程是程序运行在数据集上的动态过程，进程是一个动态实体，它应创建而产生，应调度执行因等待资源或事件而被处于等待状态，因完成任务而被撤消</li>
<li>进程是系统进行资源分配和调度的一个独立单位
4．一个程序对应多个进程，一个进程为多个程序服务（两者之间是多对多的关系）</li>
<li>一个程序执行在不同的数据集上就成为不同的进程，可以用进程控制块来唯一地标识每个进程</li>
</ol>
<h1 id="线程-">线程</h1>
<h2 id="线程定义-">线程定义</h2>
<ol>
<li>
<p>线程是操作系统调度的最小单位</p>
</li>
<li>
<p>它被包含在进程之中，是进程中的实际运作单位</p>
</li>
<li>
<p>进程本身是无法自己执行的，要操作cpu，必须创建一个线程，线程是一系列指令的集合</p>
</li>
<li>
<p>线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位</p>
</li>
<li>
<p>一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务</p>
</li>
<li>
<p>无论你启多少个线程，你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行</p>
</li>
<li>
<p>进程本身是无法自己执行的，要操作cpu，必须创建一个线程，线程是一系列指令的集合</p>
</li>
<li>
<p>所有在同一个进程里的线程是共享同一块内存空间的，不同进程间内存空间不同</p>
</li>
<li>
<p>同一个进程中的各线程可以相互访问资源，线程可以操作同进程中的其他线程，但进程仅能操作子进程</p>
</li>
<li>
<p>两个进程想通信，必须要通过一个中间代理</p>
</li>
<li>
<p>对主线程的修改可能回影响其他子线程，对主进程修改不会影响其他进程因为进程间内存相互独立，但是同一进程下的线程共享内存</p>
</li>
<li>
<p>进程和线程的区别
1、进程包含线程
2、线程共享内存空间
3、进程内存是独立的（不可互相访问）
4、进程可以生成子进程，子进程之间互相不能互相访问（相当于在父级进程克隆两个子进程）
5、在一个进程里面线程之间可以交流。两个进程想通信，必须通过一个中间代理来实现
6、创建新线程很简单，创建新进程需要对其父进程进行克隆。
7、一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
8、父进程可以修改不影响子进程，但不能修改。
9、线程可以帮助应用程序同时做几件事</p>
</li>
<li>
<p>for循环同时启动多个线程
import threading
import time</p>
</li>
</ol>
<p>def sayhi(num): #定义每个线程要运行的函数
print(&ldquo;running on number:%s&rdquo; %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=(&lsquo;t-%s&rsquo;%i,))
t.start()
4. t.join()： 实现所有线程都执行结束后再执行主线程
import threading
import time
start_time = time.time()</p>
<p>def sayhi(num): #定义每个线程要运行的函数
print(&ldquo;running on number:%s&rdquo; %num)
time.sleep(3)
t_objs = []    #将进程实例对象存储在这个列表中
for i in range(50):
t = threading.Thread(target=sayhi,args=(&lsquo;t-%s&rsquo;%i,))
t.start()          #启动一个线程，程序不会阻塞
t_objs.append(t)
print(threading.active_count())    #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
t.join()     #阻塞某个程序
print(threading.current_thread())    #打印执行这个命令进程</p>
<p>print(&quot;&mdash;&mdash;&mdash;&mdash;&mdash;-all threads has finished&hellip;..&quot;)
print(threading.active_count())
print(&lsquo;cost time:',time.time() - start_time)
5. setDaemon(): 守护线程，主线程退出时，需要子线程随主线程退出
import threading
import time
start_time = time.time()</p>
<p>def sayhi(num): #定义每个线程要运行的函数
print(&ldquo;running on number:%s&rdquo; %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=(&lsquo;t-%s&rsquo;%i,))
t.setDaemon(True)  #把当前线程变成守护线程，必须在t.start()前设置
t.start()          #启动一个线程，程序不会阻塞
print(&lsquo;cost time:',time.time() - start_time)
6. GIL全局解释器锁：保证同一时间仅有一个线程对资源有操作权限
作用：在一个进程内，同一时刻只能有一个线程执行
说明：python多线程中GIL锁只是在CPU操作时（如：计算）才是串行的，其他都是并行的，所以比串行快很多
1）为了解决不同线程同时访问同一资源时，数据保护问题，而产生了GIL
2）GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行，而不管你的程序里实际开了多少条线程
3）为了解决这个问题，CPython自己定义了一个全局解释器锁，同一时间仅仅有一个线程可以拿到这个数据
4）python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程，就是C接口
5）但是这仅仅是CPython这个版本的问题，在PyPy，中就没有这种缺陷
7. 线程锁
1）当一个线程对某个资源进行CPU计算的操作时加一个线程锁，只有当前线程计算完成主动释放锁，其他线程才能对其操作
2）这样就可以防止还未计算完成，释放GIL锁后其他线程对这个资源操作导致混乱问题
用户锁使用举例
import time
import threading
lock = threading.Lock()          #1 生成全局锁
def addNum():
global num                  #2 在每个线程中都获取这个全局变量
print('&ndash;get num:',num )
time.sleep(1)
lock.acquire()              #3 修改数据前加锁
num  -= 1                   #4 对此公共变量进行-1操作
lock.release()              #5 修改后释放
8. Semaphore(信号量)</p>
<ol>
<li>互斥锁 同时只允许一个线程更改数据，而Semaphore是同时允许一定数量的线程更改数据</li>
<li>比如厕所有3个坑，那最多只允许3个人上厕所，后面的人只能等里面有人出来了才能再进去</li>
<li>作用就是同一时刻允许运行的线程数量</li>
<li>线程池实现并发
import requests
from concurrent.futures import ThreadPoolExecutor</li>
</ol>
<p>def fetch_request(url):
result = requests.get(url)
print(result.text)</p>
<p>url_list = [
&lsquo;https://www.baidu.com&rsquo;,
&lsquo;https://www.google.com/',         #google页面会卡住，知道页面超时后这个进程才结束
&lsquo;http://dig.chouti.com/',          #chouti页面内容会直接返回，不会等待Google页面的返回
]</p>
<p>pool = ThreadPoolExecutor(10)            # 创建一个线程池，最多开10个线程
for url in url_list:
pool.submit(fetch_request,url)       # 去线程池中获取一个线程，线程去执行fetch_request方法</p>
<p>pool.shutdown(True)                      # 主线程自己关闭，让子线程自己拿任务执行
1.7 协程</p>
<ol>
<li>什么是协程（进入上一次调用的状态）</li>
<li>协程，又称微线程，纤程，协程是一种用户态的轻量级线程。</li>
<li>线程的切换会保存到CPU的栈里，协程拥有自己的寄存器上下文和栈，</li>
<li>协程调度切换时，将寄存器上下文和栈保存到其他地方，在切回来的时候，恢复先前保存的寄存器上下文和栈</li>
<li>协程能保留上一次调用时的状态（即所有局部状态的一个特定组合），每次过程重入时，就相当于进入上一次调用的状态</li>
<li>协程最主要的作用是在单线程的条件下实现并发的效果，但实际上还是串行的（像yield一样）</li>
<li>协程缺点(无法利用多核资源)
协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上线程阻塞（Blocking）操作（如IO时）会阻塞掉整个程序</li>
<li>协程为何能处理大并发1：Greenlet遇到I/O手动切换</li>
<li>协程之所以快是因为遇到I/O操作就切换（最后只有CPU运算）</li>
<li>这里先演示用greenlet实现手动的对各个协程之间切换</li>
<li>其实Gevent模块仅仅是对greenlet的再封装，将I/O间的手动切换变成自动切换</li>
<li>协程为何能处理大并发2：Gevent遇到I/O自动切换</li>
<li>Gevent 是一个第三方库，可以轻松通过gevent实现并发同步或异步编程</li>
<li>在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程</li>
<li>Greenlet全部运行在主程序操作系统进程的内部，但它们被协作式地调度。</li>
<li>Gevent原理是只要遇到I/O操作就会自动切换到下一个协程</li>
<li>使用协程处理并发
注：Gevent只用起一个线程，当请求发出去后gevent就不管,永远就只有一个线程工作，谁先回来先处理
import gevent
import requests
from gevent import monkey</li>
</ol>
<p>monkey.patch_all()</p>
<h1 id="这些请求谁先回来就先处理谁">这些请求谁先回来就先处理谁</h1>
<p>def fetch_async(method, url, req_kwargs):
response = requests.request(method=method, url=url, **req_kwargs)
print(response.url, response.content)</p>
<h1 id="-发送请求-">##### 发送请求</h1>
<p>gevent.joinall([
gevent.spawn(fetch_async, method='get&rsquo;, url='https://www.python.org/', req_kwargs={}),
gevent.spawn(fetch_async, method='get&rsquo;, url='https://www.google.com/', req_kwargs={}),
gevent.spawn(fetch_async, method='get&rsquo;, url='https://github.com/', req_kwargs={}),
])</p>

</div>


    </main>

    
  </body>
</html>
